--
-- TIMER.LUA
--
-- Timer state logic
--
-- This code is read in for the one and only timer state, and wakes up any
-- other states as it sees fit.
--
-- Author: Asko Kauppi <akauppi@gmail.com>
--
--[[
===============================================================================

Copyright (C) 2008 Asko Kauppi <akauppi@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

===============================================================================
]]--

assert( timer_sleep )
local table_remove= assert( table.remove )

--
-- { { linda_h, key_val, wakeup_at_ms [,period_ms] },
--      ...
-- }
--
local collection= {}


--
-- { [linda_h]= { key_val= tbl, ... },
--      ...
-- }
--
-- Lookup that leads to the right table within 'collection'
--
local lookup= {}


-----
-- set_timer( linda_h, key_val [,wakeup_at_ms [,period_ms]] )
--
-- Sets up a timer, or switches it off ('wakeup_at'==nil)
--
function set_timer( linda, key, wakeup_at, period )
    
    local t1= lookup[linda]
    if not t1 then
        t1= {}
        lookup[linda]= t1
    end
    
    local t2= t1[key]

    if wakeup_at then
        if not t2 then
            t2= { linda, key }
            t1[key]= t2
        end
        t2[3]= wakeup_at
        t2[4]= period

    elseif t2 then
        -- 't2' is a table within 'collection'; take it out
        --
        for i=1,#collection do
            if collection[i]==t2 then
                table_remove( collection,i )
                break
            end
        end
    end
end


-----
-- void= check_timers( now_ms )
--
-- Check timers, and wake up the ones expired (should be at least one).
--
function check_timers( now )
    local take_out

    for i=1,#collection do
        local t= collection[i]
        if t[3] <= now then
            -- signal the Linda, renew the timings
            --
            t[1]:set( t[2], now )
            
            local period= t[4]

            if period then
                -- There is a period, repeat the timer
                -- (skip any beats that are already gone)
                repeat
                    t[3]= t[3]+period
                until t[3]>now
            else
                -- No period, take it out
                take_out= take_out or {}
                table.insert( take_out, 1, i )  -- build in reversed order
            end
        end
    end

    for _,j in ipairs(take_out) do    
        table.remove( collection, j )
    end
end    


-----
-- Snore loop
--
while true do

    -- When it's time to wake up someone:
    --
    --      now_ms= timer_sleep()
    --
    -- ..or when there's a change in the timers:
    --
    --      linda, key, wake_at_ms= timer_sleep()
    --
    local a,b,c= timer_sleep( 0.0 )

    if b~=nil then
        set_timer( a,b,c )
    else
        check_timers(a)
    end
end

